/*
 * Copyright 2009 Gary Brown
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 *
 * Change History:
 * 7 Mar 2009 : Initial version created by gary
 */
package org.betonthemove.platform.betfair;

import java.io.*;
import java.util.Calendar;
import java.util.logging.Level;

import org.apache.http.HttpEntity;
import org.apache.http.HttpHost;
import org.apache.http.HttpResponse;
import org.apache.http.client.*;
import org.apache.http.client.methods.*;
import org.apache.http.impl.client.DefaultHttpClient;

import org.betonthemove.platform.*;

public class BetfairBettingPlatform implements BettingPlatform {

	private static final String BFEXCHANGE_SERVICE = "/exchange/v5/BFExchangeService";

	private static final String CONTENT_TYPE = "application/xml";

	private static final String SOAPACTION = "SOAPAction";

	private static final String API_BETFAIR_COM = "api.betfair.com";

	private static final String BFGLOBAL_SERVICE = "/global/v3/BFGlobalService";

	private static final String PRODUCT_ID = "684";

	private static final String REMAINING_CANCELLED = "REMAINING_CANCELLED";

	private static final String OK = "OK";

	public BetfairBettingPlatform() {
	}
	
	public void login(String username, String password)
					throws BettingException {
		logger.info("Logging in");
		
		java.util.Map<String, String> params=new java.util.HashMap<String, String>();
		params.put("locationId", "0");
		params.put("username", username);
		params.put("password", password);
		params.put("productId", PRODUCT_ID);
		params.put("vendorSoftwareId", "0");
		params.put("ipAddress", "0");
		
		String data=buildGlobalServiceRequest("login", params);
		
    	try {
            HttpClient httpclient = new DefaultHttpClient();

        	HttpHost host=new HttpHost(API_BETFAIR_COM, 443, "https");
        	
            HttpPost httppost = new HttpPost(BFGLOBAL_SERVICE);
            
            org.apache.http.entity.StringEntity entity=
            	new org.apache.http.entity.StringEntity(data);
            entity.setContentType(CONTENT_TYPE);
            
            httppost.setEntity(entity);
            
            httppost.setHeader(SOAPACTION, "login");
            
            HttpResponse response = httpclient.execute(host,httppost);
            HttpEntity resEntity = response.getEntity();

            if (resEntity != null) {
            	ByteArrayOutputStream baos=new ByteArrayOutputStream();
            	
            	resEntity.writeTo(baos);
            	
            	byte[] b=baos.toByteArray();
            	
            	baos.close();
            	
            	ByteArrayInputStream is=new ByteArrayInputStream(b);
            	
            	Object res=MessageDecoder.getMessage(is);
            	
            	if (res instanceof SOAPEnvelope &&
            			((SOAPEnvelope)res).getBody() != null) {
            		Response resp=((SOAPEnvelope)res).getBody().getResponse();
            		
            		if (resp != null && resp.getResult() != null &&
            				resp.getResult().getHeader() != null) {
            			
            			if (resp.getResult().getErrorCode().equals(OK)) {
            				m_session = resp.getResult().getHeader().getSessionToken();
            				
            				m_currency = resp.getResult().getCurrency();
            				
            				if (m_currency == null) {
            					m_currency = "";
            				}
            				
            			} else {
            				throw new BettingException("Unable to sign in: "+
            						resp.getResult().getErrorCode());
            			}
            			
            		} else {
            			throw new BettingException("No response returned");
            		}
            	}
            }
	    	
		} catch(BettingException be) {
			throw be;
		} catch(Exception e) {
			String mesg=e.getLocalizedMessage();
			
			if (mesg == null) {
				mesg = e.toString();
			}
			
			throw new BettingException("Failed to login: "+mesg, e);
		}
	}
	
	protected String buildGlobalServiceRequest(String action, java.util.Map<String,String> params) {
    	String ret="<?xml version=\"1.0\" encoding=\"utf-8\"?>"+
    		"<soap:Envelope xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\" "+
    		"xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\">\r\n"+
    		"<soap:Body>\r\n"+
    		"<"+action+" xmlns=\"http://www.betfair.com/publicapi/v3/BFGlobalService/\">\r\n"+
    		"<request>\r\n";
    	
    	if (m_session != null) {
    		ret += "<header>\r\n"+
    				"<clientStamp>0</clientStamp>\r\n"+
    				"<sessionToken>"+m_session+"</sessionToken>\r\n"+
    				"</header>\r\n";
    	}
    	
    	java.util.Iterator<String> iter=params.keySet().iterator();
    	
    	while (iter.hasNext()) {
    		String key=iter.next();
    		String value=params.get(key);
    	
    		ret += "<"+key+">"+value+"</"+key+">\r\n";
    	}
    	
    	ret += "</request>\r\n"+
    		"</"+action+">\r\n"+
    		"</soap:Body>\r\n"+
    		"</soap:Envelope>";    	

    	return(ret);
	}

	protected String buildExchangeServiceRequest(String action, java.util.Map<String,String> params) {
    	String ret="<?xml version=\"1.0\" encoding=\"utf-8\"?>"+
    		"<soap:Envelope xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\" "+
    		"xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\">\r\n"+
    		"<soap:Body>\r\n"+
    		"<"+action+" xmlns=\"http://www.betfair.com/publicapi/v5/BFExchangeService/\">\r\n"+
    		"<request>\r\n";
    	
    	if (m_session != null) {
    		ret += "<header>\r\n"+
    				"<clientStamp>0</clientStamp>\r\n"+
    				"<sessionToken>"+m_session+"</sessionToken>\r\n"+
    				"</header>\r\n";
    	}
    	
    	java.util.Iterator<String> iter=params.keySet().iterator();
    	
    	while (iter.hasNext()) {
    		String key=iter.next();
    		String value=params.get(key);
    	
    		ret += "<"+key+">"+value+"</"+key+">\r\n";
    	}
    	
    	ret += "</request>\r\n"+
    		"</"+action+">\r\n"+
    		"</soap:Body>\r\n"+
    		"</soap:Envelope>";    	

    	return(ret);
	}

	public boolean isConnected() {
		return(m_session != null);
	}

	public void logout() throws BettingException {
		logger.info("Logging out");
		
		java.util.Map<String, String> params=new java.util.HashMap<String, String>();

		String data=buildGlobalServiceRequest("logout", params);
		
    	try {
            HttpClient httpclient = new DefaultHttpClient();

        	HttpHost host=new HttpHost(API_BETFAIR_COM, 443, "https");
        	
            HttpPost httppost = new HttpPost(BFGLOBAL_SERVICE);
            
            org.apache.http.entity.StringEntity entity=
            	new org.apache.http.entity.StringEntity(data);
            entity.setContentType(CONTENT_TYPE);
            
            httppost.setEntity(entity);
            
            httppost.setHeader(SOAPACTION, "logout");
            
            HttpResponse response = httpclient.execute(host,httppost);
            HttpEntity resEntity = response.getEntity();

            if (resEntity != null) {
            	ByteArrayOutputStream baos=new ByteArrayOutputStream();
            	
            	resEntity.writeTo(baos);
            	
            	byte[] b=baos.toByteArray();
            	
            	logger.finest("RESULT: "+new String(b));
            	
            	baos.close();
            	
            	ByteArrayInputStream is=new ByteArrayInputStream(b);
            	
            	Object res=MessageDecoder.getMessage(is);
            	
            	if (res instanceof SOAPEnvelope &&
            			((SOAPEnvelope)res).getBody() != null) {
            		Response resp=((SOAPEnvelope)res).getBody().getResponse();
            		
            		if (resp != null && resp.getResult() != null &&
            				resp.getResult().getHeader() != null) {
            			
            			if (resp.getResult().getErrorCode().equals(OK) == false) {
            				throw new BettingException("Unable to sign out: "+
            						resp.getResult().getErrorCode());
            			}
            			
            		} else {
            			throw new BettingException("No response returned");
            		}
            	}
            }

            m_session = null;
            m_currency = "";
            
            m_eventTypes = null;
	    	
		} catch(BettingException be) {
			throw be;
		} catch(Exception e) {
			String mesg=e.getLocalizedMessage();
			
			if (mesg == null) {
				mesg = e.toString();
			}
			
			throw new BettingException("Failed to logout: "+mesg, e);
		}
	}
	
	public String getCurrency() throws BettingException {
		return(m_currency);
	}

	public AccountFunds getAccountFunds() throws BettingException {
		AccountFunds ret=null;
		
		java.util.Map<String, String> params=new java.util.HashMap<String, String>();

		String data=buildExchangeServiceRequest("getAccountFunds", params);
		
		try {
            HttpClient httpclient = new DefaultHttpClient();

        	HttpHost host=new HttpHost(API_BETFAIR_COM, 443, "https");
        	
            HttpPost httppost = new HttpPost(BFEXCHANGE_SERVICE);
            
            org.apache.http.entity.StringEntity entity=
            	new org.apache.http.entity.StringEntity(data);
            entity.setContentType(CONTENT_TYPE);
            
            httppost.setEntity(entity);
            
            httppost.setHeader(SOAPACTION, "getAccountFunds");
            
            HttpResponse response = httpclient.execute(host,httppost);
            HttpEntity resEntity = response.getEntity();

            if (resEntity != null) {
            	ByteArrayOutputStream baos=new ByteArrayOutputStream();
            	
            	resEntity.writeTo(baos);
            	
            	byte[] b=baos.toByteArray();
            	
            	logger.finest("RESULT: "+new String(b));
            	
            	baos.close();
            	
            	ByteArrayInputStream is=new ByteArrayInputStream(b);
            	
            	Object res=MessageDecoder.getMessage(is);
            	
            	if (res instanceof SOAPEnvelope &&
            			((SOAPEnvelope)res).getBody() != null) {
            		
            		Response resp=((SOAPEnvelope)res).getBody().getResponse();
            		
            		if (resp != null && resp.getResult() != null &&
            				resp.getResult().getHeader() != null) {
            			
            			if (resp.getResult().getErrorCode().equals(OK) == false) {
            				throw new BettingException("Unable to get account funds: "+
            						resp.getResult().getErrorCode());
            			}
            			
            			ret = new BetfairAccountFunds(resp.getResult().getAvailBalance(),
            					resp.getResult().getBalance());
            			
            			m_session = resp.getResult().getHeader().getSessionToken();

            		} else {
            			throw new BettingException("No response returned");
            		}
            	}
            }
	        
		} catch(BettingException be) {
			throw be;
		} catch(Exception e) {
			String mesg=e.getLocalizedMessage();
			
			if (mesg == null) {
				mesg = e.toString();
			}
			
			throw new BettingException("Failed to get account funds: "+mesg, e);
		}

		return(ret);
	}

	protected void keepAlive() {
		
		logger.info("Keep alive");
		
		try {
		} catch(Exception e) {
			e.printStackTrace();
		}
	}
	
	public java.util.List<EventType> getEventTypes()
					throws BettingException {
		
		if (m_eventTypes == null) {			
			m_eventTypes = new java.util.Vector<EventType>();
		
			java.util.Map<String, String> params=new java.util.HashMap<String, String>();
	
			String data=buildExchangeServiceRequest("getActiveEventTypes", params);
			
			try {
	            HttpClient httpclient = new DefaultHttpClient();
	
	        	HttpHost host=new HttpHost(API_BETFAIR_COM, 443, "https");
	        	
	            HttpPost httppost = new HttpPost(BFGLOBAL_SERVICE);
	            
	            org.apache.http.entity.StringEntity entity=
	            	new org.apache.http.entity.StringEntity(data);
	            entity.setContentType(CONTENT_TYPE);
	            
	            httppost.setEntity(entity);
	            
	            httppost.setHeader(SOAPACTION, "getActiveEventTypes");
	            
	            HttpResponse response = httpclient.execute(host,httppost);
	            HttpEntity resEntity = response.getEntity();
	
	            if (resEntity != null) {
	            	ByteArrayOutputStream baos=new ByteArrayOutputStream();
	            	
	            	resEntity.writeTo(baos);
	            	
	            	byte[] b=baos.toByteArray();
	            	
	            	logger.finest("RESULT: "+new String(b));
	            	
	            	baos.close();
	            	
	            	ByteArrayInputStream is=new ByteArrayInputStream(b);
	            	
	            	Object res=MessageDecoder.getMessage(is);
	            	
	            	if (res instanceof SOAPEnvelope &&
	            			((SOAPEnvelope)res).getBody() != null) {
	            		
	            		Response resp=((SOAPEnvelope)res).getBody().getResponse();
	            		
	            		if (resp != null && resp.getResult() != null &&
	            				resp.getResult().getHeader() != null) {
	            			
	            			if (resp.getResult().getErrorCode().equals(OK) == false) {
	            				throw new BettingException("Unable to get event types: "+
	            						resp.getResult().getErrorCode());
	            			}
	            			
	            			m_eventTypes.addAll(resp.getResult().getEventTypeItems());
	            			
	            			// Filter out unwanted event types
	            			for (int i=m_eventTypes.size()-1; i >= 0; i--) {
	            				boolean filter=false;
	            				
	            				for (int j=0; filter == false &&
	            						j < m_filteredEventTypeIds.length; j++) {
	            					filter = (m_eventTypes.get(i).getId() == 
	            								m_filteredEventTypeIds[j]);
	            				}
	            				
	            				if (filter) {
	            					m_eventTypes.remove(i);
	            				}
	            			}
	            			
	            			m_session = resp.getResult().getHeader().getSessionToken();
	            			
	            		} else {
	            			throw new BettingException("No response returned");
	            		}
	            	}
	            }
		        
			} catch(BettingException be) {
				throw be;
			} catch(Exception e) {
				String mesg=e.getLocalizedMessage();
				
				if (mesg == null) {
					mesg = e.toString();
				}
				
				throw new BettingException("Failed to get event types: "+mesg, e);
			}
		}

		return(m_eventTypes);
	}

	public MarketGroup getMarkets(int eventTypeId,
			int daysLookahead, java.util.List<String> countries)
					throws BettingException {
		java.util.List<Market> mlist=new java.util.Vector<Market>();
	
		java.util.Map<String, String> params=new java.util.HashMap<String, String>();
		params.put("eventTypeIds", "<int>"+eventTypeId+"</int>");
		
		if (countries != null && countries.size() > 0) {
			String countriesText="";
			
			for (int i=0; i < countries.size(); i++) {
				countriesText += "<Country>"+countries.get(i)+"</Country>";
			}
			
			params.put("countries", countriesText);
		}
		
		if (daysLookahead > 0) {
			String toDate=getToDate(daysLookahead);
			
			if (toDate != null) {
				params.put("toDate", toDate);
			}
		}

		String data=buildExchangeServiceRequest("getAllMarkets", params);
		
		try {
            HttpClient httpclient = new DefaultHttpClient();

        	HttpHost host=new HttpHost(API_BETFAIR_COM, 443, "https");
        	
            HttpPost httppost = new HttpPost(BFEXCHANGE_SERVICE);
            
            org.apache.http.entity.StringEntity entity=
            	new org.apache.http.entity.StringEntity(data);
            entity.setContentType(CONTENT_TYPE);
            
            httppost.setEntity(entity);
            
            httppost.setHeader(SOAPACTION, "getAllMarkets");
            
            HttpResponse response = httpclient.execute(host,httppost);
            HttpEntity resEntity = response.getEntity();

            if (resEntity != null) {
            	ByteArrayOutputStream baos=new ByteArrayOutputStream();
            	
            	resEntity.writeTo(baos);
            	
            	byte[] b=baos.toByteArray();
            	
            	logger.finer("RESULT: "+new String(b));
            	
            	baos.close();
            	
            	ByteArrayInputStream is=new ByteArrayInputStream(b);
            	
            	Object res=MessageDecoder.getMessage(is);
            	
            	if (res instanceof SOAPEnvelope &&
            			((SOAPEnvelope)res).getBody() != null) {
            		
            		Response resp=((SOAPEnvelope)res).getBody().getResponse();
            		
            		if (resp != null && resp.getResult() != null &&
            				resp.getResult().getHeader() != null) {
            			
            			if (resp.getResult().getErrorCode().equals(OK) == false) {
            				throw new BettingException("Unable to get markets: "+
            						resp.getResult().getErrorCode());
            			}
            			
            			java.util.List<Market> markets=
            				decodeMarketData(resp.getResult().getMarketData());
            			
            			if (markets == null) {
            				throw new BettingException("No markets found");
            			}
            			
            			mlist.addAll(markets);
            			
            			m_session = resp.getResult().getHeader().getSessionToken();
            			
            		} else {
            			throw new BettingException("No response returned");
            		}
            	}
            }
	        
		} catch(BettingException be) {
			throw be;
		} catch(Exception e) {
			String mesg=e.getLocalizedMessage();
			
			if (mesg == null) {
				mesg = e.toString();
			}
			
			throw new BettingException("Failed to get markets: "+mesg, e);
		}
		
		// Get market group
		m_groupMarkets.clear();
		m_groups.clear();
		
		MarketGroup ret=MarketGroup.build(mlist, m_groupMarkets, m_groups);
		
		return(ret);
	}
	
	public java.util.List<MarketGroup> getSubGroups(MarketGroup group) {
		return(m_groups.get(group));
	}
	
	public java.util.List<Market> getMarkets(MarketGroup group) {
		return(m_groupMarkets.get(group));
	}

	public static java.util.List<Market> decodeMarketData(String marketData) {
		java.util.List<Market> ret=new java.util.Vector<Market>();

		if (marketData != null) {
			String[] markets=marketData.split(":");

			for (int i=0; i < markets.length; i++) {

				String[] fields=markets[i].split("~");

				if (fields.length > 15) {
					try {
						BetfairMarket ma=new BetfairMarket();
	
						ma.setMarketId(Integer.parseInt(fields[0]));
						ma.setMarketName(fields[1]);
						ma.setMarketType(fields[2]);
						ma.setMarketStatus(fields[3]);
						
						ma.setMenuPath(fields[5]);
	
						ma.setBetDelay(Integer.parseInt(fields[7]));
	
						if (fields[15].equals("Y")) {
							ma.setDueInPlay(true);
						}
	
						ma.setTotalAmountMatched(Double.parseDouble(fields[13]));
	
						ma.setStartDate(Long.parseLong(fields[4]));
	
						ret.add(ma);
					} catch(Exception e) {
						e.printStackTrace();
					}
				}
			}
		}

		return(ret);
	}

	public MarketDetails getMarketDetails(int marketId,
					boolean updateOnly)	throws BettingException {
		MarketDetails ret=null;
		java.util.Map<Integer,String> nameMap=new java.util.HashMap<Integer,String>();
		
		try {
			if (updateOnly == false) {
				MarketEntrantInfo mei=getMarketEntrantInfo(marketId);
				
				nameMap = mei.getEntrantNames();
			}
			
			java.util.Map<String, String> params=new java.util.HashMap<String, String>();
			params.put("marketId", ""+marketId);

			String data=buildExchangeServiceRequest("getCompleteMarketPricesCompressed", params);
			
            HttpClient httpclient = new DefaultHttpClient();

        	HttpHost host=new HttpHost(API_BETFAIR_COM, 443, "https");
        	
            HttpPost httppost = new HttpPost(BFEXCHANGE_SERVICE);
            
            org.apache.http.entity.StringEntity entity=
            	new org.apache.http.entity.StringEntity(data);
            entity.setContentType(CONTENT_TYPE);
            
            httppost.setEntity(entity);
            
            httppost.setHeader(SOAPACTION, "getCompleteMarketPricesCompressed");
            
            HttpResponse response = httpclient.execute(host,httppost);
            HttpEntity resEntity = response.getEntity();

            if (resEntity != null) {
            	ByteArrayOutputStream baos=new ByteArrayOutputStream();
            	
            	resEntity.writeTo(baos);
            	
            	byte[] b=baos.toByteArray();
            	
            	logger.finest("RESULT: "+new String(b));
            	
            	baos.close();
            	
            	ByteArrayInputStream is=new ByteArrayInputStream(b);
            	
            	Object res=MessageDecoder.getMessage(is);
            	
            	if (res instanceof SOAPEnvelope &&
            			((SOAPEnvelope)res).getBody() != null) {
            		
        			Response resp=((SOAPEnvelope)res).getBody().getResponse();
            		
            		if (resp != null && resp.getResult() != null &&
            				resp.getResult().getHeader() != null) {
            			
            			if (resp.getResult().getErrorCode().equals(OK) == false) {
            				throw new BettingException("Unable to get market entrants: "+
            						resp.getResult().getErrorCode());
            			}
            			
            			String marketPrices=resp.getResult().getCompleteMarketPrices();
            			
            			if (marketPrices == null) {
        	        		logger.warning("MarketPrices = null: "+
        	        				resp.getResult().getErrorCode()+
        	        				" errorCode="+resp.getResult().getHeader().getErrorCode()+
        	        				" minErrorCode="+resp.getResult().getHeader().getMinorErrorCode());
        	        	} else {
        	        	
        	        		ret = buildMarketDetails(marketPrices,
        	        				nameMap);
                			
                			m_session = resp.getResult().getHeader().getSessionToken();
        	        	}
            			
            		} else {
            			throw new BettingException("No response returned");
            		}
            	}
            }

		} catch(BettingException be) {
			throw be;
		} catch(Exception e) {
			String mesg=e.getLocalizedMessage();
			
			if (mesg == null) {
				mesg = e.toString();
			}
			
			throw new BettingException("Failed to get market entrants: "+
					mesg, e);
		}

		if (ret == null) {
			ret = new MarketDetails(EMPTY_ENTRANT_LIST, 0);
		}
		
		return(ret);
	}
	
	protected MarketDetails buildMarketDetails(String marketPrices,
						java.util.Map<Integer,String> nameMap) {
		String[] entrantPriceDetails=marketPrices.split(":");
		java.util.List<MarketEntrant> entrants=
					new java.util.Vector<MarketEntrant>();
		
		String[] fields=entrantPriceDetails[0].split("~");
		
		int betDelay=Integer.parseInt(fields[1]);
		
		for (int i=1; i < entrantPriceDetails.length; i++) {
			
			logger.finer("ENTRANT PRICE DETAILS["+i+"] = "+entrantPriceDetails[i]);
			
			String[] segments=entrantPriceDetails[i].split("\\|");
			
			if (segments.length != 2) {
				logger.severe("Market price for runners does not have 2 regions: "+segments.length);
			}
			
			fields = segments[0].split("~");
			
			int selectionId=Integer.parseInt(fields[0]);
			
			int orderIndex=Integer.parseInt(fields[1]);
			
			double totalMatched=0.0;
			
			if (fields[2].trim().length() > 0) {
				totalMatched = Double.parseDouble(fields[2]);
			}
			
			double lastPriceMatched=0.0;
			
			if (fields[3].trim().length() > 0) {
				lastPriceMatched = Double.parseDouble(fields[3]);
			}
			
			BetfairMarketEntrant entrant=new BetfairMarketEntrant();
			
			entrant.setSelectionId(selectionId);
			entrant.setName(nameMap.get(selectionId));

			entrant.setLastPriceMatched(lastPriceMatched);
			entrant.setTotalMatched(totalMatched);
			entrant.setTimestamp(System.currentTimeMillis());
			
			entrant.setOrderIndex(orderIndex);
			
			entrants.add(entrant);
			
			if (segments.length == 2) {

				fields = segments[1].split("~");
				
				double currentBackOdds=0.0;
				double currentLayOdds=0.0;
				double currentBackPrice=0.0;
				double currentLayPrice=0.0;
				
				for (int k=0; k < fields.length; k+=5) {
					
					double odds=Double.parseDouble(fields[k]);
					double back=Double.parseDouble(fields[k+1]);
					double lay=Double.parseDouble(fields[k+2]);
					
					if (back > 0.0) {
						if (back >= 2.0) {
							currentBackOdds = odds;
							currentBackPrice = back;
						}
					} else if (back == 0.0 && currentLayOdds == 0.0) {
						if (lay >= 2.0) {
							currentLayOdds = odds;
							currentLayPrice = lay;
						}
					}
				}
				
				entrant.setCurrentBackOdds(currentBackOdds);
				entrant.setCurrentLayOdds(currentLayOdds);
				entrant.setCurrentBackPrice(currentBackPrice);
				entrant.setCurrentLayPrice(currentLayPrice);
			}
		}
		
		java.util.Collections.sort(entrants, m_entrantComparator);
		
		MarketDetails ret=new MarketDetails(entrants, betDelay);
		
		return(ret);
	}
	
	/**
	 * This method obtains the market entrant information.
	 * 
	 * @param marketId The market id
	 * @return The market entrant info
	 * @throws BettingException Failed to get market entrant info
	 */
	protected MarketEntrantInfo getMarketEntrantInfo(int marketId)
									throws BettingException {
		MarketEntrantInfo ret=null;

		try {
			java.util.Map<String, String> params=new java.util.HashMap<String, String>();
			params.put("marketId", ""+marketId);
	
			String data=buildExchangeServiceRequest("getMarket", params);
			
	        HttpClient httpclient = new DefaultHttpClient();
	
	    	HttpHost host=new HttpHost(API_BETFAIR_COM, 443, "https");
	    	
	        HttpPost httppost = new HttpPost(BFEXCHANGE_SERVICE);
	        
	        org.apache.http.entity.StringEntity entity=
	        	new org.apache.http.entity.StringEntity(data);
	        entity.setContentType(CONTENT_TYPE);
	        
	        httppost.setEntity(entity);
	        
	        httppost.setHeader(SOAPACTION, "getMarket");
	        
	        HttpResponse response = httpclient.execute(host,httppost);
	        HttpEntity resEntity = response.getEntity();
	
	        if (resEntity != null) {
	        	ByteArrayOutputStream baos=new ByteArrayOutputStream();
	        	
	        	resEntity.writeTo(baos);
	        	
	        	byte[] b=baos.toByteArray();
	        	
	        	logger.finest("RESULT: "+new String(b));
	        	
	        	baos.close();
	        	
	        	ByteArrayInputStream is=new ByteArrayInputStream(b);
	        	
	        	Object res=MessageDecoder.getMessage(is);
	        	
	        	if (res instanceof SOAPEnvelope &&
	        			((SOAPEnvelope)res).getBody() != null) {
	        		
	    			Response resp=((SOAPEnvelope)res).getBody().getResponse();
	        		
	        		if (resp != null && resp.getResult() != null &&
	        				resp.getResult().getHeader() != null) {
	        			
	        			if (resp.getResult().getErrorCode().equals(OK) == false) {
	        				throw new BettingException("Unable to get market entrants: "+
	        						resp.getResult().getErrorCode());
	        			}
	        			
	        			String marketName = resp.getResult().getMarket().getName();
	        			java.util.Map<Integer,String> nameMap=
	        					new java.util.HashMap<Integer, String>();
	        			
				        for (int i=0; i < resp.getResult().getMarket().getRunners().size(); i++) {
				        	nameMap.put(new Integer(resp.getResult().getMarket().getRunners().get(i).getSelectionId()),
				        			resp.getResult().getMarket().getRunners().get(i).getName());
				        }
				        
				        ret = new MarketEntrantInfo(marketId, marketName,
				        					nameMap);
	        			
	        			m_session = resp.getResult().getHeader().getSessionToken();
	        			
	        		} else {
	        			throw new BettingException("No response returned");
	        		}
	        	}
	        }
		} catch(BettingException be) {
			throw be;
		} catch(Exception e) {
			String mesg=e.getLocalizedMessage();
			
			if (mesg == null) {
				mesg = e.toString();
			}
			
			throw new BettingException("Failed to get entrant information: "+
							mesg, e);
		}

        return(ret);
	}
	
	public java.util.List<Bet> getBets() throws BettingException {
		java.util.List<Bet> ret=new java.util.Vector<Bet>();
		
		java.util.Map<String, String> params=new java.util.HashMap<String, String>();
		params.put("betStatus", "MU");
		params.put("recordCount", "200");
		params.put("startRecord", "0");
		params.put("orderBy", "PLACED_DATE");
		params.put("sortOrder", "ASC");

		String data=buildExchangeServiceRequest("getMUBets", params);
		
		try {
            HttpClient httpclient = new DefaultHttpClient();

        	HttpHost host=new HttpHost(API_BETFAIR_COM, 443, "https");
        	
            HttpPost httppost = new HttpPost(BFEXCHANGE_SERVICE);
            
            org.apache.http.entity.StringEntity entity=
            	new org.apache.http.entity.StringEntity(data);
            entity.setContentType(CONTENT_TYPE);
            
            httppost.setEntity(entity);
            
            httppost.setHeader(SOAPACTION, "getMUBets");
            
            HttpResponse response = httpclient.execute(host,httppost);
            HttpEntity resEntity = response.getEntity();

            if (resEntity != null) {
            	ByteArrayOutputStream baos=new ByteArrayOutputStream();
            	
            	resEntity.writeTo(baos);
            	
            	byte[] b=baos.toByteArray();
            	
            	logger.finest("RESULT: "+new String(b));
            	
            	baos.close();
            	
            	ByteArrayInputStream is=new ByteArrayInputStream(b);
            	
            	Object res=MessageDecoder.getMessage(is);
            	
            	if (res instanceof SOAPEnvelope &&
            			((SOAPEnvelope)res).getBody() != null) {
            		
            		Response resp=((SOAPEnvelope)res).getBody().getResponse();
            		
            		if (resp != null && resp.getResult() != null &&
            				resp.getResult().getHeader() != null) {
            			
            			if (resp.getResult().getErrorCode().equals(OK) == false) {
            				throw new BettingException("Unable to get bets: "+
            						resp.getResult().getErrorCode());
            			}
            			
            			// Get market and entrant names
            			java.util.Map<Integer,MarketEntrantInfo> marketInfo=
            					new java.util.HashMap<Integer,MarketEntrantInfo>();
            			
            			for (int i=0; i < resp.getResult().getBets().size(); i++) {
            				BetItem bet=resp.getResult().getBets().get(i);
            				
            				try {
            					MarketEntrantInfo mei=marketInfo.get(bet.getMarketId());
         
            					if (mei == null) {
            						mei = getMarketEntrantInfo(bet.getMarketId());
            						
            						marketInfo.put(bet.getMarketId(), mei);
            					}
            					
            					bet.setMarketName(mei.getMarketName());
            					bet.setEntrantName(mei.getEntrantNames().
            							get(bet.getEntrantId()));
            				} catch(BettingException be) {
            					logger.log(Level.SEVERE, "Failed to get market info '"+
            							bet.getMarketId()+"'", be);
            				}
            			}
            			
            			ret.addAll(resp.getResult().getBets());
            			
            			m_session = resp.getResult().getHeader().getSessionToken();
            		} else {
            			throw new BettingException("No response returned");
            		}
            	}
            }
	        
		} catch(BettingException be) {
			throw be;
		} catch(Exception e) {
			throw new BettingException("Failed to get bets", e);
		}

		return(ret);
	}

	public Bet placeBet(int marketId, int entrantId, boolean back,
				double odds, double stake) throws BettingException {
		Bet ret=null;
		
		String betText="<PlaceBets>"+
					"<asianLineId>0</asianLineId>"+
					"<betCategoryType>E</betCategoryType>"+
					"<betType>"+(back?"B":"L")+"</betType>"+
					"<betPersistenceType>NONE</betPersistenceType>"+
					"<marketId>"+marketId+"</marketId>"+
					"<price>"+odds+"</price>"+
					"<selectionId>"+entrantId+"</selectionId>"+
					"<size>"+stake+"</size>"+
				"</PlaceBets>";
		
		java.util.Map<String, String> params=new java.util.HashMap<String, String>();
		params.put("bets", betText);

		String data=buildExchangeServiceRequest("placeBets", params);
		
		try {
            HttpClient httpclient = new DefaultHttpClient();

        	HttpHost host=new HttpHost(API_BETFAIR_COM, 443, "https");
        	
            HttpPost httppost = new HttpPost(BFEXCHANGE_SERVICE);
            
            org.apache.http.entity.StringEntity entity=
            	new org.apache.http.entity.StringEntity(data);
            entity.setContentType(CONTENT_TYPE);
            
            httppost.setEntity(entity);
            
            httppost.setHeader(SOAPACTION, "placeBets");
            
            HttpResponse response = httpclient.execute(host,httppost);
            HttpEntity resEntity = response.getEntity();

            if (resEntity != null) {
            	ByteArrayOutputStream baos=new ByteArrayOutputStream();
            	
            	resEntity.writeTo(baos);
            	
            	byte[] b=baos.toByteArray();
            	
            	logger.finest("RESULT: "+new String(b));
            	
            	baos.close();
            	
            	ByteArrayInputStream is=new ByteArrayInputStream(b);
            	
            	Object res=MessageDecoder.getMessage(is);
            	
            	if (res instanceof SOAPEnvelope &&
            			((SOAPEnvelope)res).getBody() != null) {
            		
            		Response resp=((SOAPEnvelope)res).getBody().getResponse();
            		
            		if (resp != null && resp.getResult() != null &&
            				resp.getResult().getHeader() != null) {
            			
            			if (resp.getResult().getErrorCode().equals(OK) == false) {
            				throw new BettingException("Unable to place bet: "+
            						resp.getResult().getErrorCode());
            			}
            			
            			if (resp.getResult().getBetResults().size() == 1) {
            				ret = resp.getResult().getBetResults().get(0);
            				
            				if (resp.getResult().getBetResults().get(0).getResultCode().equals(OK) == false) {
            					throw new BettingException("Bet result not ok: "+
            							resp.getResult().getBetResults().get(0).getResultCode());
            				}
            				
            				((BetResultItem)ret).setMarketId(marketId);
            				((BetResultItem)ret).setEntrantId(entrantId);
            			} else {
            				throw new BettingException("No bet result returned");
            			}
            			
            			m_session = resp.getResult().getHeader().getSessionToken();
            			
            		} else {
            			throw new BettingException("No response returned");
            		}
            	}
            }
	        
		} catch(BettingException be) {
			throw be;
		} catch(Exception e) {
			String mesg=e.getLocalizedMessage();
			
			if (mesg == null) {
				mesg = e.toString();
			}
			
			throw new BettingException("Failed to place bet: "+mesg, e);
		}

		return(ret);
	}

	public void cancelBet(long betId) throws BettingException {
		java.util.Map<String, String> params=new java.util.HashMap<String, String>();
		
		String cancelText="<CancelBets><betId>"+betId+"</betId></CancelBets>";
		params.put("bets", cancelText);
		
		String data=buildExchangeServiceRequest("cancelBets", params);
		
		try {
			HttpClient httpclient = new DefaultHttpClient();
			
			HttpHost host=new HttpHost(API_BETFAIR_COM, 443, "https");
			
			HttpPost httppost = new HttpPost(BFEXCHANGE_SERVICE);
			
			org.apache.http.entity.StringEntity entity=
				new org.apache.http.entity.StringEntity(data);
			entity.setContentType(CONTENT_TYPE);
			
			httppost.setEntity(entity);
			
			httppost.setHeader(SOAPACTION, "cancelBets");
			
			HttpResponse response = httpclient.execute(host,httppost);
			HttpEntity resEntity = response.getEntity();
			
			if (resEntity != null) {
				ByteArrayOutputStream baos=new ByteArrayOutputStream();
				
				resEntity.writeTo(baos);
				
				byte[] b=baos.toByteArray();
				
				logger.finest("RESULT: "+new String(b));
				
				baos.close();
				
				ByteArrayInputStream is=new ByteArrayInputStream(b);
				
				Object res=MessageDecoder.getMessage(is);
				
				if (res instanceof SOAPEnvelope &&
						((SOAPEnvelope)res).getBody() != null) {
					
					Response resp=((SOAPEnvelope)res).getBody().getResponse();
					
					if (resp != null && resp.getResult() != null &&
							resp.getResult().getHeader() != null) {
						
						if (resp.getResult().getErrorCode().equals(OK) == false) {
							throw new BettingException("Unable to cancel bet: "+
									resp.getResult().getErrorCode());
						}
						
						if (resp.getResult().getBetResults().size() != 1) {
							throw new BettingException("No cancel bet result returned");
						}
						
						if (resp.getResult().getBetResults().get(0).getResultCode().
								equals(REMAINING_CANCELLED) == false) {
							throw new BettingException("Remaining balance not cancelled: "+
									resp.getResult().getBetResults().get(0).getResultCode());
						}
						
						m_session = resp.getResult().getHeader().getSessionToken();
						
					} else {
						throw new BettingException("No response returned");
					}
				}
			}
		
		} catch(BettingException be) {
			throw be;
		} catch(Exception e) {
			String mesg=e.getLocalizedMessage();
			
			if (mesg == null) {
				mesg = e.toString();
			}
			
			throw new BettingException("Failed to cancel bet: "+mesg, e);
		}
	}

	protected String getToDate(int daysLookahead) {
		java.util.Date today=new java.util.Date();
		java.util.Calendar cal=Calendar.getInstance();
		cal.setTime(today);
		cal.add(Calendar.DAY_OF_MONTH, daysLookahead-1);
		
		String ret=""+cal.get(Calendar.YEAR)+"-";
		int month=cal.get(Calendar.MONTH)+1;
		
		if (month < 10) {
			ret += "0";
		}
				
		ret += month+"-";
		
		int day=cal.get(Calendar.DAY_OF_MONTH);
		
		if (day < 10) {
			ret += "0";
		}
					
		ret += day+"T"+"23:59:59.000Z";
		
		return(ret);
	}
	
	private java.util.logging.Logger logger=java.util.logging.Logger.getLogger(BetfairBettingPlatform.class.getName());

	private String m_session=null;
	private String m_currency=null;
	private java.util.List<EventType> m_eventTypes=null;
	private java.util.Map<MarketGroup,java.util.List<Market>> m_groupMarkets=
			new java.util.HashMap<MarketGroup, java.util.List<Market>>();
	private java.util.Map<MarketGroup,java.util.List<MarketGroup>> m_groups=
			new java.util.HashMap<MarketGroup, java.util.List<MarketGroup>>();

	private static final EntrantComparator m_entrantComparator=new EntrantComparator();
	private static final int[] m_filteredEventTypeIds=new int[] {
		13, 14, 15
	};
	
	private static final java.util.List<MarketEntrant> EMPTY_ENTRANT_LIST=
					new java.util.Vector<MarketEntrant>();
	
	public static class EntrantComparator implements java.util.Comparator<MarketEntrant> {

		public int compare(MarketEntrant o1, MarketEntrant o2) {
			if (o1 instanceof BetfairMarketEntrant &&
					o2 instanceof BetfairMarketEntrant) {
				return(((BetfairMarketEntrant)o1).getOrderIndex() -
						((BetfairMarketEntrant)o2).getOrderIndex());
			}
			return 0;
		}
		
	}
	
	public static class MarketEntrantInfo {
		
		public MarketEntrantInfo(int marketId, String marketName,
						java.util.Map<Integer,String> entrantNames) {
			m_marketId = marketId;
			m_marketName = marketName;
			m_entrantNames = entrantNames;
		}
		
		public String getMarketName() {
			return(m_marketName);
		}
		
		public int getMarketId() {
			return(m_marketId);
		}
		
		public java.util.Map<Integer, String> getEntrantNames() {
			return(m_entrantNames);
		}
		
		private int m_marketId=0;
		private String m_marketName=null;
		private java.util.Map<Integer,String> m_entrantNames=null;
	}
}
